﻿/*
** Xin YUAN, 2023, BSD (2)
*/

////////////////////////////////////////////////////////////////////////////////

#include <sys/wait.h>

////////////////////////////////////////////////////////////////////////////////

//_x_child_process

class _x_child_process
{
public:
	_x_child_process() noexcept : m_pid(-1)
	{
	}
	_x_child_process(const _x_child_process&) = delete;
	_x_child_process(_x_child_process&& src) noexcept : m_pid(src.m_pid)
	{
		src.m_pid = -1;
	}
	~_x_child_process() noexcept
	{
	}

	_x_child_process& operator=(const _x_child_process&) = delete;
	_x_child_process& operator=(_x_child_process&& src) noexcept
	{
		if ( this != &src ) {
			assert( m_pid == -1 );
			m_pid = src.m_pid;
			src.m_pid = -1;
		}
		return *this;
	}

	void ResetPid() noexcept
	{
		m_pid = -1;
	}

	bool Create(const char* szCmd) noexcept
	{
		assert( m_pid == -1 );

		pid_t pid = ::fork();
		if ( pid == -1 )
			return false;  //errno

		//in child
		if ( pid == 0 ) {
			//pid_t child_current_pid = ::getpid();
			_exec_cmd(szCmd);
		}

		//in parent
		m_pid = pid;
		return true;
	}

/*
another get state:
kill(pid, 0) == 0 --- running;
-1, errno == ESRCH --- stopped;
others --- error
*/

	//-1: error, 0: stopped, 1: running
	//  iExitCode : 127 --- child process creation failed
	int GetState(int& iExitCode) const noexcept
	{
		assert( m_pid != -1 );

		iExitCode = -1;

		int status = 0;
		pid_t pid = ::waitpid(m_pid, &status, WNOHANG | WUNTRACED | WCONTINUED);
		if ( pid == -1 )
			return -1;  //errno
		if ( pid == 0 )
			return 1;  //state no changed
		if ( WIFEXITED(status) ) {
			iExitCode = WEXITSTATUS(status);
			return 0;
		}
		//if ( WIFSIGNALED(status) )
			//int signo = WTERMSIG(status);
		if ( WIFSTOPPED(status) )
			//int signo = WSTOPSIG(status);
			return 1;
		if ( WIFCONTINUED(status) ) //SIGCONT
			return 1;

		//killed by signal
		return 0;
	}

	//iExitCode : 127 --- child process creation failed
	void WaitForEnd(int& iExitCode) noexcept
	{
		assert( m_pid != -1 );

		iExitCode = -1;

		//signals

		//SIGCHLD: no processing

		struct sigaction saNew = { 0 };
		saNew.sa_handler = SIG_IGN;
		saNew.sa_flags = 0;

		struct sigaction saOldINT = { 0 };
		int retINT = ::sigaction(SIGINT, &saNew, &saOldINT);
		//0 -- succeeded, -1 -- errno

		struct sigaction saOldQUIT = { 0 };
		int retQUIT = ::sigaction(SIGQUIT, &saNew, &saOldQUIT);
		//0 -- succeeded, -1 -- errno

		//wait
		int status = 0;
		do {
			pid_t pid = ::waitpid(m_pid, &status, WUNTRACED | WCONTINUED);
			if ( pid == -1 ) { //errno
				if ( errno == EINTR )
					continue;
				break;
			}
			if ( WIFEXITED(status) ) {
				iExitCode = WEXITSTATUS(status);
				break;
			}
			if ( WIFSIGNALED(status) ) {
				//int signo = WTERMSIG(status);
				break;
			}
			if ( WIFSTOPPED(status) )
				//int signo = WSTOPSIG(status);
				continue;
			if ( WIFCONTINUED(status) ) //SIGCONT
				continue;
		} while ( true );

		//restore
		if ( retINT == 0 )
			::sigaction(SIGINT, &saOldINT, NULL);  //no check
		if ( retQUIT == 0 )
			::sigaction(SIGQUIT, &saOldQUIT, NULL);  //no check
	}

	bool Kill() noexcept
	{
		assert( m_pid != -1 );
		int ret = ::kill(m_pid, SIGTERM);
		return ret == 0;  //-1, errno
	}

protected:
	void _exec_cmd(const char* szCmd) noexcept
	{
		int ret = ::execl("/bin/bash", "bash", "-c", szCmd, (char*)NULL);
		if ( ret == -1 ) //errno
			::_exit(127);
	}

protected:
	pid_t m_pid;
};

//_x_rio_process

class _x_rio_process : public _x_child_process
{
private:
	typedef _x_child_process  baseClass;

public:
	_x_rio_process() noexcept : m_fd_stdout_rd(-1), m_fd_stdin_wr(-1)
	{
	}
	_x_rio_process(const _x_rio_process&) = delete;
	_x_rio_process(_x_rio_process&& src) noexcept : baseClass(rv_forward(static_cast<baseClass&>(src))),
							m_fd_stdout_rd(src.m_fd_stdout_rd),
							m_fd_stdin_wr(src.m_fd_stdin_wr)
	{
		src.m_fd_stdout_rd = -1;
		src.m_fd_stdin_wr = -1;
	}
	~_x_rio_process() noexcept
	{
		ClosePipeStdout();
		ClosePipeStdin();
	}

	_x_rio_process& operator=(const _x_rio_process&) = delete;
	_x_rio_process& operator=(_x_rio_process&& src) noexcept
	{
		baseClass::operator=(rv_forward(static_cast<baseClass&>(src)));
		if ( this != &src ) {
			ClosePipeStdout();
			ClosePipeStdin();
			m_fd_stdout_rd = src.m_fd_stdout_rd;
			m_fd_stdin_wr = src.m_fd_stdin_wr;
			src.m_fd_stdout_rd = -1;
			src.m_fd_stdin_wr = -1;
		}
		return *this;
	}

	void ClosePipeStdout() noexcept
	{
		if ( m_fd_stdout_rd != -1 ) {
			int ret = ::close(m_fd_stdout_rd);
			(void)ret;
			assert( ret == 0 );  //-1, errno
			m_fd_stdout_rd = -1;
		}
	}
	void ClosePipeStdin() noexcept
	{
		if ( m_fd_stdin_wr != -1 ) {
			int ret = ::close(m_fd_stdin_wr);
			(void)ret;
			assert( ret == 0 );  //-1, errno
			m_fd_stdin_wr = -1;
		}
	}

	bool Create(const char* szCmd) noexcept
	{
		assert( m_pid == -1 );
		assert( m_fd_stdout_rd == -1 && m_fd_stdin_wr == -1 );

		int fd_stdout[2], fd_stdin[2];

		//pipe
		int ret = 0;
		ret = ::pipe(fd_stdout);
		if ( ret == -1 )
			return false;  //errno
		ret = ::pipe(fd_stdin);
		if ( ret == -1 ) {
			//errno
			ret = ::close(fd_stdout[0]);
			assert( ret == 0 );
			ret = ::close(fd_stdout[1]);
			assert( ret == 0 );
			return false;
		}

		//create process
		pid_t pid = ::fork();
		if ( pid == -1 ) {
			//errno
			ret = ::close(fd_stdout[0]);
			assert( ret == 0 );
			ret = ::close(fd_stdout[1]);
			assert( ret == 0 );
			ret = ::close(fd_stdin[0]);
			assert( ret == 0 );
			ret = ::close(fd_stdin[1]);
			assert( ret == 0 );
			return false;
		}

		//in child
		if ( pid == 0 ) {
			//pid_t child_current_pid = ::getpid();
			//redirect
			ret = ::close(fd_stdout[0]);  //read end
			assert( ret == 0 );
			ret = ::close(fd_stdin[1]);  //write end
			assert( ret == 0 );
			if ( fd_stdin[0] != STDIN_FILENO ) {
				if ( ::dup2(fd_stdin[0], STDIN_FILENO) != STDIN_FILENO ) {
					//-1, errno
					ret = ::close(fd_stdout[1]);
					assert( ret == 0 );
					ret = ::close(fd_stdin[0]);
					assert( ret == 0 );
					::_exit(126);
				}
			}
			if ( fd_stdout[1] != STDOUT_FILENO ) {
				if ( ::dup2(fd_stdout[1], STDOUT_FILENO) != STDOUT_FILENO ) {
					//-1, errno
					ret = ::close(fd_stdout[1]);
					assert( ret == 0 );
					ret = ::close(fd_stdin[0]);
					assert( ret == 0 );
					::_exit(126);
				}
			}
			if ( fd_stdout[1] != STDERR_FILENO ) {
				if ( ::dup2(fd_stdout[1], STDERR_FILENO) != STDERR_FILENO ) {
					//-1, errno
					ret = ::close(fd_stdout[1]);
					assert( ret == 0 );
					ret = ::close(fd_stdin[0]);
					assert( ret == 0 );
					::_exit(126);
				}
			}
			ret = ::close(fd_stdout[1]);
			assert( ret == 0 );
			ret = ::close(fd_stdin[0]);
			assert( ret == 0 );
			//exec
			_exec_cmd(szCmd);
		}

		//in parent
		ret = ::close(fd_stdout[1]);  //write end
		assert( ret == 0 );
		ret = ::close(fd_stdin[0]);  //read end
		assert( ret == 0 );
		m_fd_stdout_rd = fd_stdout[0];
		m_fd_stdin_wr = fd_stdin[1];
		//pid
		m_pid = pid;
		return true;
	}

	//may be blocked
	//0 -- file end, -1 -- errno
	ssize_t ReadFromPipe(void* buf, size_t count) noexcept
	{
		assert( m_fd_stdout_rd != -1 );
		ssize_t ret = 0;
		while ( true ) {
			ret = ::read(m_fd_stdout_rd, buf, count);
			if ( ret == -1 ) {
				if( errno == EINTR )
					continue;
			}
			break;
		}
		return ret;
	}
	//may be blocked
	//-1 -- errno
	ssize_t WriteToPipe(const void* buf, size_t count) noexcept
	{
		assert( m_fd_stdin_wr != -1 );
		ssize_t ret = 0;
		while ( true ) {
			ret = ::write(m_fd_stdin_wr, buf, count);
			if ( ret == -1 ) {
				if( errno == EINTR )
					continue;
			}
			break;
		}
		return ret;
	}

protected:
	int m_fd_stdout_rd;
	int m_fd_stdin_wr;
};

////////////////////////////////////////////////////////////////////////////////
